package org.reliablesource.prism.ui.plugin;

/**
 *
 *  Copyright (c) 2006, Reliable Source, Inc. All Rights Reserved
 *
 *	Created on: Mar 11, 2006
 *	@author Dennis Park <a href="mailto:dennis.park@gmail.com">dennis.park@gmail.com</a>
 *
 */

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Preferences.IPropertyChangeListener;
import org.eclipse.core.runtime.Preferences.PropertyChangeEvent;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.CoolItem;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Widget;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.reliablesource.prism.core.IPrismScreen;
import org.reliablesource.prism.core.IPrismTutorial;
import org.reliablesource.prism.ui.adapters.PrismElementAdapterFactory;
import org.reliablesource.prism.ui.util.PrismUIConstants;

public class PrismPlugin extends AbstractUIPlugin implements IPropertyChangeListener {

	// The shared instance.
	private static PrismPlugin plugin;

	private PrismElementAdapterFactory prismElementAdapterFactory;

	private IAdapterManager adapterManager;

	private Hashtable<String,ImageDescriptor > imgTable;

	private Map<String, Widget> compositeContextMap;

	private static String DIRECTORY_PREF;

	public final static String EMPTY_STRING  = "";
	public final static String CONTEXT_NOT_FOUND  = "Context not found for given widget";
	
	public final Map<Widget, Stack<Event>> widgetContext;
	
	/**
	 * The constructor.
	 */
	public PrismPlugin() {
		plugin = this;
		DIRECTORY_PREF = EMPTY_STRING;
		widgetContext = new ConcurrentHashMap<Widget, Stack<Event>>();
	}

	/**
	 * This method is called upon plug-in activation
	 */
	public void start(BundleContext context) throws Exception {
		super.start(context);

		prismElementAdapterFactory = PrismElementAdapterFactory.getInstance();
		adapterManager = Platform.getAdapterManager();
		
		adapterManager.registerAdapters
			(prismElementAdapterFactory, IPrismTutorial.class);
		
		adapterManager.registerAdapters
			(prismElementAdapterFactory, IPrismScreen.class);
		
		compositeContextMap = new HashMap<String, Widget>();
		imgTable = new Hashtable<String,ImageDescriptor > ();

		PrismPlugin.getDefault().getPluginPreferences().addPropertyChangeListener(this);
	}

	/**
	 * This method is called when the plug-in is stopped
	 */
	public void stop(BundleContext context) throws Exception {

		adapterManager.unregisterAdapters
			(prismElementAdapterFactory, IPrismTutorial.class);
		adapterManager.unregisterAdapters
			(prismElementAdapterFactory,IPrismScreen.class);
		
		PrismPlugin.getDefault().getPluginPreferences().removePropertyChangeListener(this);
		
		adapterManager = null;
		
		super.stop(context);
		plugin = null;
	}

	/**
	 * Returns the shared instance.
	 */
	public static PrismPlugin getDefault() {
		return plugin;
	}

	/**
	 * Returns an image descriptor for the image file at the given plug-in
	 * relative path.
	 * 
	 * @param path
	 *            the path
	 * @return the image descriptor
	 */
	public static ImageDescriptor getImageDescriptor(String path) {
		return AbstractUIPlugin.imageDescriptorFromPlugin(
				"org.reliablesource.prism.ui", path);
	}

	public PrismElementAdapterFactory getPrismElementAdapterFactory() {
		return prismElementAdapterFactory;
	}
	
	/**
	 * Save SWT image with the given an absolute path and format. Currently only
	 * support bmp file, since this is a stable format for current eclipse
	 * version. Suppose eclipse can write other formats without any error, we
	 * can release this constrain.
	 * 
	 * @param _data
	 *            iamge data to be saved
	 * @param _name
	 *            absolute path of image file
	 * @param _format
	 *            image format
	 */
	public void saveSWTImage(ImageData _data, String _name,
			String _format) {
		ImageLoader	imageLoader = new ImageLoader();
		
		imageLoader.data = new ImageData[] { _data };
		
		// default save format
		int _saveFormat = SWT.IMAGE_PNG;

		if (_format == null)
			_format = "png";
		else {
			_saveFormat = mapSuffixToType(_format);
			_name += "." + _format;
		}

		try {
//			FileOutputStream _outStream = new FileOutputStream(PrismPlugin.getDefault().getSavePath() +_name);
//			imageLoader.save(_outStream, _saveFormat);
			imageLoader.save(PrismPlugin.getDefault().getSavePath() +_name, _saveFormat);
		} catch (IllegalArgumentException _iae) {
			_iae.printStackTrace();
		}catch(SWTException _swte){
			_swte.printStackTrace();
		}
	}

	/**
	 * Map the suffix of the image file to the swt image type.
	 * 
	 * @param _suffix
	 *            suffix of image file
	 * @return SWT image type
	 */
	public static int mapSuffixToType(String _suffix) {
		int result = SWT.IMAGE_GIF;
		if (_suffix == null)
			return result;
		if (_suffix.equalsIgnoreCase("gif")) {
			result = SWT.IMAGE_GIF;
		} else if (_suffix.equalsIgnoreCase("jpeg")
				|| _suffix.equalsIgnoreCase("png")
				|| _suffix.equalsIgnoreCase("jpg")) {
			result = SWT.IMAGE_JPEG;
		} else if (_suffix.equalsIgnoreCase("bmp")) {
			result = SWT.IMAGE_BMP;
		} else if (_suffix.equalsIgnoreCase("png")) {
			result = SWT.IMAGE_PNG;
		} else if (_suffix.equalsIgnoreCase("ico")) {
			result = SWT.IMAGE_ICO;
		}
		return result;
	}

	public ImageDescriptor readScreen(final IPrismScreen _screen) {
		if (imgTable.containsKey(_screen.getName().trim()))
			return imgTable.get(_screen.getName().trim());

		StringBuffer _buffer = new StringBuffer();
		String _imgPath = _screen.getImageFilePath();
		
		if(_imgPath == null){
			_imgPath = getSavePath();
		}
		
		_buffer.append(_imgPath + _screen.getName() + "." + _screen.getImageFileType());
		
		ImageLoader loader = new ImageLoader();
		
		ImageData[] _imageDataArray = loader.load(_buffer.toString().trim());
		imgTable.put(_screen.getName().trim(), ImageDescriptor
				.createFromImageData(_imageDataArray[0]));
		return imgTable.get(_screen.getName().trim());
	}

	public boolean addImage(String key, ImageDescriptor img) {
		boolean imageAdded = false;
		if (!imgTable.containsValue(img)) {
			imgTable.put(key, img);
			imageAdded = true;
		}
		return imageAdded;
	}

	public ImageDescriptor getImage(String _key) {
		if (imgTable.contains(_key))
			return imgTable.get(_key);
		return null;
	}

	public void smaller() {
		System.gc();
		if (false) {
			Set keys = imgTable.keySet();
			Iterator it = keys.iterator();
			while (it.hasNext()) {
				imgTable.remove(it.next());
			}
		}
	}

	public boolean addWidget(String _key, Widget _widget) {
		if (!compositeContextMap.containsKey(_key))
			if (!compositeContextMap.containsValue(_widget)) {
				compositeContextMap.put(_key, _widget);
				return true;
			}
		return false;
	}
	
	public synchronized Map<Widget, Stack<Event>> getWidgetContext(){
		return widgetContext;
	}
	
	public void dumpWidgetContextMap(){
//		
//		new Thread(){
//			public void run(){
				Iterator<Widget> _it = widgetContext.keySet().iterator();
				while(_it.hasNext()){
					Widget _w = _it.next();
					int _nEvents = widgetContext.get(_w).size();
					Event[] _eventStack = widgetContext.get(_w).toArray(new Event[_nEvents]);
					System.err.println(_w.toString());
					for(int i = 0; i < _nEvents; i++){
						System.err.println("   " + getEventLiteral(_eventStack[i].type)+ "  :  "+  _eventStack[i].toString());
					}
				}
//			}
//		}.start();
		
	}
	


	public String getWidgetType(Widget _widget){
		String _type = EMPTY_STRING;
		if (_widget instanceof Button)
			_type = "Button";
	else if (_widget instanceof Text)
			_type="Text";
	else	if(_widget instanceof MenuItem)
		_type="MenuItem";
	else if (_widget instanceof ToolItem)
		_type="ToolItem";
	else if (_widget instanceof CoolItem)
		_type="CoolItem";		
	else if (_widget instanceof Item) 
		_type="Item";		
	else if (_widget instanceof Shell) 
		_type="Shell";				
	else
		_type="Control";		
		return _type;
	}
	
	public String getEventLiteral(int _type) {
		String _typeLiteral = "type not found.";
		switch (_type) {
		case SWT.Deactivate:
			_typeLiteral = "Deactivate";
			break;
		case SWT.Activate:
			_typeLiteral = "Activate";
			break;
		case SWT.MenuDetect:
			_typeLiteral = "MenuDetect";
			break;
		case SWT.Selection:
			_typeLiteral = "Selection";
			break;
		case SWT.Dispose:
			_typeLiteral = "Dispose";
			break;
		case SWT.Arm:
			_typeLiteral = "Arm";
			break;
		case SWT.MouseDown:
			_typeLiteral = "MouseDown";
			break;
		case SWT.MouseUp:
			_typeLiteral = "MouseUp";
			break;			
		case SWT.FocusIn:
			_typeLiteral = "FocusIn";
			break;
		case SWT.KeyUp:
			_typeLiteral = "KeyUp";
			break;
		case SWT.KeyDown:
			_typeLiteral = "KeyDown";
			break;			
		case SWT.Show:
			_typeLiteral = "Show";
			break;			
		case SWT.Expand:
			_typeLiteral = "Expand";
			break;
		case SWT.Collapse:
			_typeLiteral = "Collapse";
			break;
		case SWT.DragDetect:
			_typeLiteral = "Drag";
			break;
		case SWT.DefaultSelection:
			_typeLiteral = "Default Selection";
			break;
		default:
			break;
		}
		return _typeLiteral;
	}
	
	
	public String getWidgetContextString(Widget _widget){
		if (compositeContextMap.containsValue(_widget)){
			Iterator<String> _it  =compositeContextMap.keySet().iterator();
			while(_it.hasNext()){
				String _key = _it.next();
				Widget _w = compositeContextMap.get(_key);
				if(_widget == _w)
					return _key;
			}
		}return CONTEXT_NOT_FOUND;
	}

	public void dumpCompositeContextMap() {

		System.err.println("----Dumping Context Map----");
		Iterator it = compositeContextMap.keySet().iterator();
		StringBuffer buffer = new StringBuffer();
		while (it.hasNext()) {
			String widgetContext = ((String) it.next());
			System.err.println("Widget Context: " + widgetContext);
			Widget widget = compositeContextMap.get(widgetContext);
			if (widget instanceof Control)
				System.err.println(buffer.toString()
						+ ((Control) widget).toString() + ":"
//						+ ((Control) widget).getToolTipText() + " "
						+ ((Control) widget).handle);
			else if (widget instanceof Composite)
				printCompositeContext((Composite) widget, buffer);
		}
		System.err.println("----End of Dumping Context Map----");
	}

	public void printCompositeContext(Composite _composite, StringBuffer spaces) {
		spaces.append("   ");
		System.err.println(spaces.toString()+"----Composite Context ----");
		Control[] controls = _composite.getChildren();
		int nControls = controls.length;
		for (int j = 0; j < nControls; j++) {
			System.err.println(spaces.toString() + controls[j].toString() + ":" + controls[j].handle);
			if (controls[j] instanceof Composite) {
				if (((Composite) controls[j]).getChildren().length > 0) {
					printCompositeContext((Composite) controls[j], spaces);
					spaces.delete(spaces.length() - 3, spaces.length());
				}
			}
		}
		System.err.println(spaces.toString()+"----End Composite Context----");
	}
	
	

	public String getSavePath() {
		if(!DIRECTORY_PREF.equals(EMPTY_STRING))
			return DIRECTORY_PREF;
		
		boolean _instanceLocationIsReadOnly = Platform.getInstanceLocation().isReadOnly();
		String _location = EMPTY_STRING;
		if (_instanceLocationIsReadOnly) {
			if (Platform.getOS().trim().equals("linux")) {
				_location = PrismUIConstants.LNX_DIRECTORY_PREF;
			} else if (Platform.getOS().trim().equals("win32")) {
				_location = PrismUIConstants.WIN32_DIRECTORY_PREF;
			}
		}else{
			StringBuffer _defaultLocation = new StringBuffer();
			_defaultLocation.append(Platform.getInstanceLocation().getURL().getPath().toString().trim());
			String location = _defaultLocation.toString().trim();
			_defaultLocation.append((location.endsWith("/") || location.endsWith("\\")) ? "" : PrismUIConstants.FILE_SEPERATOR);
			
			 location+="prism_tmp" + PrismUIConstants.FILE_SEPERATOR;
			File _placeHolder = new File(location);
			if(!_placeHolder.exists())
				_placeHolder.mkdir();
			_location = location;
			DIRECTORY_PREF = _location.trim();
		}
		
		return DIRECTORY_PREF;
	
	}
	
	public void propertyChange(PropertyChangeEvent _event) {
		if( _event.getProperty().equals(PrismUIConstants.P_PATH) )
				getSavePath();
	}
	
	
	public boolean isMainShell(Shell _shell){
		if(_shell.getParent() != null)
			return false;
		return true;
	}
	
	public Shell getMainShell(Control _shell){
		if(_shell.getParent() != null)
			return getMainShell(_shell.getParent());
		return (Shell) _shell;
	}
	
	public boolean isDialog(Shell _shell){
		if((_shell.getParent() != null)
				&& (_shell.getParent() instanceof Shell))
			return true;
		return false;
	}
	
	 /**
	 * Type independent way to reflectively invoke a method .
	 *
	 * @param _object - the object whose method you want.
	 * @param _methodNameExp - the name of the method.  This can be  in the form of a RegEx. (ie. get.*[Nn]ame), or not (ie. getName).
	 * @param _args - the argument list - in sequence.
	 *
	 * @return Object - the return value of the reflectively called method, if applicable.
	 * @throws IllegalArgumentException
	 * @throws RuntimeException
	 */
	public static Object inputObjectMethodReflection(
			Object _object, String _methodNameExp, Collection _args)
			throws IllegalArgumentException, RuntimeException {
		if (null == _args)
			_args = new ArrayList();

		Class _cls = _object.getClass();
		try {
			Pattern _p = Pattern.compile(_methodNameExp);
			Method[] _clsMethods = _cls.getMethods();
			int _nMethods = _clsMethods.length;
			for (int i = 0; i < _nMethods; i++) {
				Matcher _matcher = _p.matcher(_clsMethods[i].getName());
				if (_matcher.find()) {
					Method _method = _cls.getMethod(_clsMethods[i].getName(),
							_clsMethods[i].getParameterTypes());
					return _method.invoke(_object, _args.toArray());
				}
			}
			throw new IllegalArgumentException(_cls.getName()
					+ " does not support method - " + _methodNameExp);
		} catch (final NoSuchMethodException _nsme) {
			throw new IllegalArgumentException(_cls.getName()
					+ " does not support method - " + _methodNameExp);
		} catch (final IllegalAccessException _iae) {
			throw new IllegalArgumentException(
					"Insufficient access permission to call " + _methodNameExp
							+ " in class " + _cls.getName());
		} catch (final InvocationTargetException _ite) {
			throw new RuntimeException(_ite);
		}
	}
}
